package Monopol;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;

import Cards.Card;
import Cards.Card.CardType;
import Cards.Move_card.eGoTo;
import Cards.Money_card;
import Cards.Move_card;
import GUI.GameBoard.GameBoard;
import GUI.GameLoadWindowPackage.GameLoaderFrame;
import GUI.PopUpDialog;

import GUI.SplashScreen.SplashScreen;
import GameServer.ServerService;
import Monopol.MonopolExceptions.InsufficientFunds;
import Monopol.AssetGroups.AllServices;
import Monopol.AssetGroups.AllTransportations;
import Monopol.AssetGroups.State;
import Monopol.MonopolExceptions.MonopolErrors;
import Player.Computer;
import Player.Human;
import Player.Player;
import Square.City;
import Square.Negotiable;
import Square.Square;
import Square.Square.fixedSquares;
import Utils.LoadInfo;
import Utils.Utils;
import XMLParser.MonopolyXML;
import java.util.concurrent.CountDownLatch;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author Avia Tam
 * @author Assaf Miron
 *
 */
public class Monopol {

    private static ArrayList<Player> players; // A List of all the Players in the Game
    private static Square[] squares; // A List of the Squares in the Game
    private static ArrayList<Card> warning_cards; // A List of all the Warning Cards
    private static ArrayList<Card> surprise_cards; // A List of all Surprise Cards
    public static AllServices services; // A Group of All Services
    public static AllTransportations transportations; // A Group of All Transportations
    private static State[] states; // A Group of All the States
    private static Double cashier; // The Total Amount of the Cashier Money
    public static Card saved_card; // Saves get out of prison card
    private static GameBoard gameBoard; // The Main Swing Game Board
    private static Boolean turn_over = false; // Determines if turn is over or not
    public static Boolean rollDice = false; //verify if human player wants to throw the dice
    // Define Constants
    public static final int MIN_NUMBER_OF_PLAYERS = 2;
    public static final int MAX_NUMBER_OF_PLAYERS = 6;
    private static final Integer NUMBER_OF_ALLOWED_AUCTION = 3;
    private Boolean autoDice;
    private Integer playerID;

    public Monopol(){
	// Set the Default Amount in the Cashier
	cashier = new Double(100000); // Set to 100K Shekels

	SplashScreen splashScreen = new SplashScreen(1500);
	splashScreen.showSplash();

    }

    public Double getCashier(){
	return cashier;
    }

    public void setCashier(Double newCash) {
	cashier += newCash;
    }

    /**
     * This Function will Clear all the Assets of this player from there owner
     * and Delete the Player from the Players Array
     * @param deleted_player The Player to be Deleted
     * @return The Player is Ready for Deletion
     */
    public static  Boolean deletePlayer(Player deleted_player) throws MonopolErrors {
	try {
	    // Verify that the Deleted player is a Human
	    if (deleted_player.isHuman()) // End turn of player
	    {
		setTurn_over(true);
	    }
	    // Delete the Players Assets
	    deleted_player.deleteAssets();
	    // Return the Players' Remaining Money back to the Cashier
	    cashier += deleted_player.getFunds();
	    // Verify that the Player has no more Assetes
	    if (deleted_player.getAssets().isEmpty()) {
		players.remove(deleted_player);
		
		// Return Ready to Delete
		return true;

	    } else // Could not delete all the Players Assets
	    {
		Utils.writeText("Deleting player Error!\n", deleted_player.toString());
		throw new MonopolErrors("Not All the Assets could be Cleared");
	    }
	} catch (Error e) {
	    // This Should never happen
	    throw new MonopolErrors("Something Terrible just Happened...\n" + e.toString());
	}
    }

     /**
     * Buys an Asset from the Game, Sets the Player as the Owner of this Asset
     * @param asset The asset to Buy
     * @param player The Owning Player
     */
    public void buyAsset(Negotiable asset, Player player) {
	try {
	    // Buy the Asset
	    asset.buy(player);
	    // Add the Asset to the Asset List of the Player
	    player.buyAsset(asset);
	    // Inform that the User Bought this Asset
	    Utils.writeText(player.getName(), "Just Bought", asset.getName());
	} catch (InsufficientFunds trap) {
	    if (player.isHuman()) // Printout problem only if player is human
	    {
		Utils.showError(gameBoard,trap.getMessage());
	    }
	}
    }

    /**
     * Buys a House in the City, Adds a House to where the Player is
     * @param city The City where the House was built
     * @param player The Players who built the House and owns the City
     */
    public void buyHouse(City city, Player player){
	    // Buy a House
	try {
	    // Buy a House in the City
	    city.buyHouse(player);
	    // Inform other user for this buy
	    Utils.writeText(player.getName(), "Bought a House in", city.getName(), "\nTotal number of houses in", city.getName(), "is", city.getNum_of_houses().toString() + ".");
	} catch (InsufficientFunds trap) {
	    // Print Error to Screen
	    Utils.writeText("You don't have enough money to buy a house now.");
	}
    }

    /**
     * Helper function to Get the Square Object by a Square ID
     * @param assetSquareID The Square ID on the Board
     * @return The Square Object
     */
    public Negotiable getAsset(Integer assetSquareID){
	// Verify that the Square is an Asset
	if(squares[assetSquareID].isAsset()) {
	    // Return the Square Object
	    return squares[assetSquareID];
	}
	else {
	    // Not an Asset - Return Null
	    return null;
	}
    }

    /**
     * Moves the Player in the Game To the New Location
     * @param player The Player to Move
     * @param newLocation The New Location on the Board
     */
    public void movePlayer(Player player, Integer gameCubes){
	if (player.getIs_parking() == false && player.getIs_in_prison() == false && !player.getToDelete()) {
	    // Move the Player to the Next Square in the Game
	    player.setGame_location(player.calcGameLocation(gameCubes));
	}
	else
	{
	    String where = "";
	    if(player.getIs_in_prison()) {
		where = "Prison";
	    }
	    else if(player.getIs_parking()) {
		where = "Parking";
	    }
	    else if(player.getToDelete()) {
		where = "The Trash...";
	    }
	    Utils.writeText("Player can not Move because it is in", where);
	}
    }

    /**
     * Finds a Player by Name in the Players Array
     * @param playerName The Player Name to Find
     * @return The player Object as {@link Player}
     */
    public Player findPlayer(String playerName) {
	if (playerName != null) {
	    Iterator<Player> iterator = players.iterator();
	    while (iterator.hasNext()) {
		Player chkPlayer = ((Player) iterator.next());
		if (chkPlayer.getName().equals(playerName)) {
		    return chkPlayer;
		}
	    }
	}
	return null;
    }

    /**
     * This Function Initializes the Cards and Squares
     * And sets each player in a position to start the Game
     */
    public  void initGame(String serverName, Integer serverPort) {
	try{
	// Initialize the Players Array
	players = new ArrayList<Player>();
	// Clear the New Array List - Make sure it is empty
	players.clear();
	ServerService.getInstance().setServerName(serverName, serverPort);
	    // We Assume that the XSD will not change and we will always use the Same XSD
	    MonopolyXML monopolyParser = new MonopolyXML(ServerService.getInstance().getGameBoardXML());
	    // Check if the Parsing was OK
	    if (monopolyParser.getParseOK()) {
		// Get the Warning Cards Deck
		warning_cards = monopolyParser.getWarningDeck();
		// Shuffle the Cards
		Collections.shuffle(warning_cards);
		// Get the Surprise Cards Deck
		surprise_cards = monopolyParser.getSurpriseDeck();
		// Shuffle the Cards
		Collections.shuffle(surprise_cards);
		// Get all the Transportations
		transportations = monopolyParser.getAllTransportaions();
		// Get all the Services
		services = monopolyParser.getAllServices();
		// Get the Squares Order
		squares = monopolyParser.getSquaresOrder();
	    } // The Parsing got an Error - Initialize Manualy
	    else {
		// Initialize the Game Squares
		squares = new Square[36]; // Set the Number of Squares
		transportations = new AllTransportations(); // Create the Transportation Group
		services = new AllServices(); // Create the Services Group
		// Create the Squares
		squares[fixedSquares.START_SQUARE.get()] = new Square("Start");
		Double[] stay_price = Utils.createDoubleArray((double) 250, (double) 300, (double) 350, (double) 400);
		states = new State[8];
		states[0] = new State("Canada", Color.pink);
		squares[1] = states[0].CreateCity("Montreal", stay_price, new Double(300), new Double(100));
		squares[2] = states[0].CreateCity("Toronto", stay_price, new Double(400), new Double(50));
		squares[3] = new Square("Surprise");
		states[1] = new State("England", Color.blue);
		Double[] trans_stay_price = Utils.createDoubleArray((double) 10, (double) 15);
		squares[4] = transportations.createTransportation("Dan", trans_stay_price, new Double(100));
		squares[5] = states[1].CreateCity("London", stay_price, new Double(600), new Double(100));
		squares[6] = states[1].CreateCity("Manchester", stay_price, new Double(350), new Double(25));
		squares[7] = states[1].CreateCity("Liverpool", stay_price, new Double(300), new Double(15));
		squares[8] = new Square("Warning");
		squares[fixedSquares.PRISON_SQUARE.get()] = new Square("Prison");
		states[2] = new State("Spain", Color.yellow);
		squares[10] = states[2].CreateCity("Madrid", stay_price, new Double(550), new Double(75));
		squares[11] = states[2].CreateCity("Valencia", stay_price, new Double(350), new Double(20));
		squares[12] = states[2].CreateCity("Barcelona", stay_price, new Double(500), new Double(80));
		Double[] service_stay_price = Utils.createDoubleArray((double) 20, (double) 30);
		squares[13] = services.createService("Electricity CO.", service_stay_price, new Double(120));
		squares[14] = transportations.createTransportation("Egged", trans_stay_price, new Double(100));
		states[3] = new State("Germany", Color.black);
		squares[15] = states[3].CreateCity("Berlin", stay_price, new Double(550), new Double(75));
		squares[16] = states[3].CreateCity("Munich", stay_price, new Double(350), new Double(20));
		squares[17] = states[3].CreateCity("Hamburg", stay_price, new Double(500), new Double(80));
		squares[fixedSquares.PARKING_SQUARE.get()] = new Square("Parking");
		states[4] = new State("Italy", Color.green);
		squares[19] = states[4].CreateCity("Milan", stay_price, new Double(550), new Double(75));
		squares[20] = states[4].CreateCity("Bologna", stay_price, new Double(350), new Double(20));
		squares[21] = states[4].CreateCity("Roma", stay_price, new Double(500), new Double(80));
		squares[22] = new Square("Warning");
		squares[23] = transportations.createTransportation("Kavim", trans_stay_price, new Double(100));
		states[5] = new State("China", Color.red);
		squares[24] = states[5].CreateCity("Beijing", stay_price, new Double(425), new Double(33));
		squares[25] = states[5].CreateCity("Shanghai", stay_price, new Double(350), new Double(26));
		squares[26] = states[5].CreateCity("Hong kong", stay_price, new Double(430), new Double(50));
		squares[fixedSquares.GO2PRISON_SQUARE.get()] = new Square("Go to Prison");
		states[6] = new State("Israel", Color.magenta);
		squares[28] = states[6].CreateCity("Tel aviv", stay_price, new Double(550), new Double(100));
		squares[29] = states[6].CreateCity("Haifa", stay_price, new Double(500), new Double(90));
		squares[30] = states[6].CreateCity("Jerusalem", stay_price, new Double(520), new Double(95));
		squares[31] = services.createService("Bezeq", service_stay_price, new Double(70));
		squares[32] = transportations.createTransportation("El-Al", trans_stay_price, new Double(150));
		states[7] = new State("USA", Color.cyan);
		squares[33] = states[7].CreateCity("New york", stay_price, new Double(610), new Double(100));
		squares[34] = states[7].CreateCity("Chicago", stay_price, new Double(500), new Double(90));
		squares[35] = states[7].CreateCity("Los angeles", stay_price, new Double(570), new Double(95));

		// Initializing the Cards
		//3 types of warning cards 1.go to prison ,2.go to next warning square ,3.pay
		warning_cards = new ArrayList<Card>();
		warning_cards.add(new Move_card(CardType.WARNING_CARD, "Go to prison", eGoTo.PRISON));
		warning_cards.add(new Move_card(CardType.WARNING_CARD, "Go to prison", eGoTo.PRISON));
		warning_cards.add(new Move_card(CardType.WARNING_CARD, "Go to prison", eGoTo.PRISON));
		warning_cards.add(new Move_card(CardType.WARNING_CARD, "Go to next warning square", eGoTo.NEXT_WARNING));
		warning_cards.add(new Move_card(CardType.WARNING_CARD, "Go to next warning square", eGoTo.NEXT_WARNING));
		warning_cards.add(new Move_card(CardType.WARNING_CARD, "Go to next warning square", eGoTo.NEXT_WARNING));//need to edit move card
		warning_cards.add(new Money_card(CardType.WARNING_CARD, "Pay 500 to Cashire", new Double(100), true));
		warning_cards.add(new Money_card(CardType.WARNING_CARD, "Pay 700 to Players", new Double(70), false));
		warning_cards.add(new Money_card(CardType.WARNING_CARD, "Pay 100 to Players", new Double(10), false));//trick card doesn't do as tells
		// Shuffle the Cards
		Collections.shuffle(warning_cards);
		//4 types of surprise_cards 1.go to start,2.go to next surprise square,3.get money 4.get out of prison
		surprise_cards = new ArrayList<Card>();
		surprise_cards.add(new Card(CardType.SURPRISE_CARD, "Get out of prison"));
		surprise_cards.add(new Move_card(CardType.SURPRISE_CARD, "Go to start", eGoTo.START));
		surprise_cards.add(new Move_card(CardType.SURPRISE_CARD, "Go to start", eGoTo.START));
		surprise_cards.add(new Move_card(CardType.SURPRISE_CARD, "Go to next surprise square", eGoTo.NEXT_SURPRISE));
		surprise_cards.add(new Move_card(CardType.SURPRISE_CARD, "Go to next surprise square", eGoTo.NEXT_SURPRISE));
		surprise_cards.add(new Move_card(CardType.SURPRISE_CARD, "Go to next surprise square", eGoTo.NEXT_SURPRISE));
		surprise_cards.add(new Money_card(CardType.SURPRISE_CARD, "Get 500", new Double(500), true));
		surprise_cards.add(new Money_card(CardType.SURPRISE_CARD, "Get 700 from players", new Double(70), false));
		surprise_cards.add(new Money_card(CardType.SURPRISE_CARD, "Get 100 from players", new Double(10), false));//trick card doesn't do as tells
		// Shuffle the Cards
		Collections.shuffle(surprise_cards);
	    }
	} catch (Exception e) {
	    Logger.getLogger(Monopol.class.getName()).log(Level.SEVERE, null, e);
	    PopUpDialog myDiag = new PopUpDialog(null, "The Server is not Avialable");
	    myDiag.showErrorMessage();
	    // Critical Error - Exit the Game
	    System.exit(1);
	}
    }

    /**
     * This Function Loads the Monopoly Players
     */
    public   Boolean loadGame(String defServerName, Integer defServerPort, String defPlayerName, String defDiceMode) {
	// Check the Default Dice Mode
	Boolean diceMode = defDiceMode.equalsIgnoreCase("Auto");
	// Create a New Load Window
	GameLoaderFrame load = new GameLoaderFrame(defServerName, defServerPort, defPlayerName, diceMode);
	// Create a New Count down Latcher
	CountDownLatch loadSignal = new CountDownLatch(1);
	try {
	    // Show the Window
	    load.showLoadWindow(loadSignal);
	    // Wait for the Latcher to End
	    loadSignal.await();
	} catch (InterruptedException ex) {
	    Logger.getLogger(Monopol.class.getName()).log(Level.SEVERE, null, ex);
	}
	if (load.getFinished()) {
	    // Get the Player ID from the Game
	    playerID = load.getPlayerID();
	    String playerName = load.getPlayerName();
	    // Check the Automatic Dice Roll Mode in the Game
	    autoDice = load.getAutomaticDice();
	    // Get the Players Array that was Created in the Loading Window
	    LoadInfo[] arrPlayers = load.getPlayersArray();
	    // Loop the Players Information in the Array
	    Integer counter = 0; //insert player index
	    for (LoadInfo playerInfo : arrPlayers) {
		if (playerInfo != null) {
		    // Check Each Player and Create the Players
		    if (playerInfo.getIsHuman()) {
			// Create a new Human Player
			Human player = new Human(playerInfo.getPlayerName(), playerInfo.getInitFunds(), playerInfo.getPlayerColor(), counter);
			// Check if this Player is The Curent Playing Player
			if(playerInfo.getPlayerName().equals(playerName)){
			    // Set the Unique ID for this Player
			    player.setUniqueID(playerID);
			}
			// Add the New Player to the Game
			players.add(player);
		    } else {
			// Create a new Computer Player
			Computer player = new Computer(playerInfo.getPlayerName(), playerInfo.getInitFunds(), playerInfo.getPlayerColor(), counter);
			// Add the New Player to the Game
			players.add(player);
		    }
		    counter++;//increment player index
		}
	    }
	}
	// Returns the Monopoly Load Game Finished Status
	return load.getFinished();
    }

    public Integer getPlayerID(){
	return playerID;
    }

    /**
     * Getter function for the Game Squares
     * If no Squares are loaded when this Funciotn is Called,
     * The {@link initGame()} Function is Called
     * @return The Game Squares Array
     */
    public Square[] getGameSquares(){
	if(squares == null)
	{
	    // This should Never Happen...
	    // Incase of No initialization - Revert to Defauls
	    initGame("localhost",8080);
	}
	return squares;
    }

    /**
     * A Getter for the Game Players.
     * @return The Game Players Array List
     * @throws Monopol.MonopolExceptions.MonopolErrors On Errors
     */
    public ArrayList<Player> getPlayers() throws MonopolErrors{
	if(players == null){
	    throw new MonopolErrors("No Players in the Game!");
	}
	return players;
    }

    /**
     * Returns the Square Name of the Players Location
     * @param location The Current Player Location
     * @return The Name of the Square the Player is at
     */
    public static  String getSquareName(Integer location) {
	return squares[location].getName();
    }

    /**
     * Check if a Player passed the Start Square and does it need to get paid
     * @param prevLocation The Previous Player Location
     * @param currLocation The Current Player Location
     * @return The Amount the Game Needs to Play the Player
     */
    public  Double checkGo2Start(Integer prevLocation, Integer currLocation, String player_name) {
	if (cashier > 0) {
	    // Check if The Player went pass the Start Square
	    if (prevLocation > currLocation) {
		// Check if the Player is ON the Start Square
		if (currLocation == 0) {
		    // Inform that the Player got 400 Shekels
		    Utils.writeText(player_name, "Got to the Start Square and Recieved 400 Shekels!");
		    // Reduce 400 Shekels from the Cashier
		    cashier -= 400;
		    // Update the Cash in the Game Cashier
		    gameBoard.updateCashier(cashier);
		    // Return the Player 400 Shekels for Standing on the Start Square
		    return 400.0;
		} else {
		    // Inform that the Player got 200 Shekels
		    Utils.writeText(player_name, "Recieved 200 Shekels!");
		    // Reduce 200 Shekels from the Cashier
		    cashier -= 200;
		    // Update the Cash in the Game Cashier
		    gameBoard.updateCashier(cashier);
		    // Return the Player 200 Shekels for Passing the Start Square
		    return 200.0;
		}
	    } else // Player did not pass the Start Square
	    {
		return 0.0;
	    }
	} else // No Money in the Cashier
	{
	    Utils.writeText("Sorry but there is no money left in the Bank...");
	}
	return 0.0;

    }

    public  Boolean getTurn_over() {
	return turn_over;
    }

    public static  void setTurn_over(Boolean turn_over) {
	Monopol.turn_over = turn_over;
    }


    public static  void SetRolldice(Boolean rollDice) {
	Monopol.rollDice = rollDice;

    }

    public static  Boolean getRolldice() {
	return rollDice;
    }

    /**
     * Returns the Dice Mode: Automatic Dice Roll or Manual Dice Roll
     * @return The Dice Mode
     */
    public Boolean getAutomaticDiceMode(){
	return autoDice;
    }

}
